﻿using PivotData;
using PivotIO.XML;
using System;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Web.Http;

namespace PivotWebsite.Controllers
{
    /// <summary>
    /// Control access to the facets for an image.
    /// </summary>
    public class FacetsController : ApiController
    {
        /// <summary>
        /// Return all facets associated with an image.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <returns></returns>
        public FacetAbsBase[] GetFacets(int cId, int iId)
        {
            var info = CheckInputs(cId, iId);
            if (info.Item1.Facets == null)
                return new FacetAbsBase[0];
            return info.Item1.Facets;
        }

        /// <summary>
        /// Add a new facet to the list we already have. If the identical guy is being added,
        /// then we should not re-insert it.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <param name="fname"></param>
        /// <param name="value"></param>
        public void PutFacet(int cId, int iId, string fname, string value)
        {
            if (fname == null || value == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            PutFacet(cId, iId, new FacetString() { Name = fname, Value = value });
        }

        /// <summary>
        /// Put an number facet
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <param name="fname"></param>
        /// <param name="value"></param>
        public void PutFacet(int cId, int iId, string fname, double value)
        {
            if (fname == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            PutFacet(cId, iId, new FacetNumber() { Name = fname, Number = value });
        }

        /// <summary>
        /// Put a date-time facet
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <param name="fname"></param>
        /// <param name="when"></param>
        public void PutFacet(int cId, int iId, string fname, DateTime when)
        {
            if (fname == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            PutFacet(cId, iId, new FacetDateTime() { Name = fname, Date = when });
        }

        /// <summary>
        /// Put a date-time facet
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <param name="fname"></param>
        /// <param name="when"></param>
        public void PutFacet(int cId, int iId, string fname, string linkname, Uri link)
        {
            if (fname == null || linkname == null || link == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            PutFacet(cId, iId, new FacetUri() { Name = fname, LinkString = linkname, URI = link });
        }

        /// <summary>
        /// Generic single facet placement.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        /// <param name="fnew"></param>
        private void PutFacet(int cId, int iId, FacetAbsBase fnew)
        {
            if (fnew.Name == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            var info = CheckInputs(cId, iId);
            if (info.Item1.Facets == null)
            {
                info.Item1.Facets = new FacetAbsBase[] { fnew };
            }
            else
            {
                if (!FacetNotThere(fnew, info.Item1.Facets))
                    return;
                info.Item1.Facets = info.Item1.Facets.Concat(new FacetAbsBase[] { fnew }).ToArray();
            }
            ImageInfoIO.SaveAllImages(cId, info.Item2);
        }

        /// <summary>
        /// Save multiple facets at once, with option to do a replace.
        /// </summary>
        /// <param name="cID"></param>
        /// <param name="iId"></param>
        /// <param name="facets"></param>
        public void PutFacet(int cId, int iId, [FromBody] FacetAbsBase[] facets, bool replace = false)
        {
            var info = CheckInputs(cId, iId);
            if (facets == null)
                return;

            // A dummy list to get going with

            PutFacetInternal(cId, facets, info.Item1, replace);
            ImageInfoIO.SaveAllImages(cId, info.Item2);
        }

        /// <summary>
        /// Given the looked up info and the cid, save the faces.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="facets"></param>
        /// <param name="info"></param>
        private void PutFacetInternal(int cId, FacetAbsBase[] facets, ImageInfo image, bool replace)
        {
            if (image.Facets == null)
            {
                image.Facets = new FacetAbsBase[0];
            }

            // Check the facets to make sure they are ok before we do any mods.

            foreach (var f in facets)
            {
                if (FacetBad(f))
                    throw new HttpResponseException(HttpStatusCode.BadRequest);
            }

            // Now, if we are replacing, do the work.

            if (replace)
            {
                var fnamelist = facets.Select(f => f.Name).ToArray();
                image.Facets = image.Facets.Where(f => fnamelist.All(fn => fn != f.Name)).ToArray();
            }

            // Don't know how to deal with this incase we are looking at two items coming in that are the same, so this
            // is definately a little painful!

            foreach (var f in facets)
            {
                if (FacetNotThere(f, image.Facets))
                    image.Facets = image.Facets.Concat(new FacetAbsBase[] { f }).ToArray();
            }
        }

        /// <summary>
        /// Add the list of facets to an image we already have. The imageName is considered a regular
        /// expression, and we return the number of images we manage to modify.
        /// </summary>
        /// <param name="cId">The colledtion ID</param>
        /// <param name="imageName">The name of the image, including regular expression characters</param>
        /// <param name="facets">The list of facets to modify</param>
        /// <returns>The number of images we modified</returns>
        public int PutFacet(int cId, string imageName, [FromBody] FacetAbsBase[] facets, bool replace = false)
        {
            var info = CheckInputs(cId, imageName);
            if (facets == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            // Go through all the images that need to be modified.

            int imageCount = 0;
            foreach (var image in info.Item1)
            {
                PutFacetInternal(cId, facets, image, replace);
                imageCount++;
            }

            ImageInfoIO.SaveAllImages(cId, info.Item2);
            return imageCount;
        }

        /// <summary>
        /// Delete all facets associated with an image.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        public void DeleteFacet(int cId, int iId)
        {
            var info = CheckInputs(cId, iId);
            info.Item1.Facets = new FacetAbsBase[0];
            ImageInfoIO.SaveAllImages(cId, info.Item2);
        }

        /// <summary>
        /// Return true if the facet isn't "good" (null, etc.).
        /// </summary>
        /// <param name="f"></param>
        /// <returns></returns>
        private bool FacetBad(FacetAbsBase f)
        {
            if (f.Name == null)
                return true;

            if (f is FacetString)
            {
                if ((f as FacetString).Value == null)
                    return true;
            }
            else if (f is FacetUri)
            {
                var fu = f as FacetUri;
                if (fu.URI == null || fu.LinkString == null)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Look to see if an abstract fact is actually present.
        /// </summary>
        /// <param name="fnew"></param>
        /// <param name="facetAbsBase"></param>
        /// <returns></returns>
        private bool FacetNotThere<T>(T fnew, FacetAbsBase[] facetAbsBase) where T : FacetAbsBase
        {
            return facetAbsBase.Where(f => f.Name == fnew.Name).Where(f => fnew.Equals(f)).Count() == 0;
        }

        /// <summary>
        /// Check the inputs to make sure that everything is ok.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="iId"></param>
        private Tuple<ImageInfo, ImageInfo[]> CheckInputs(int cId, int iId)
        {
            var cl = CollectionInfoIO.LoadCollection(cId);
            if (cl == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);


            var images = ImageInfoIO.LoadAllImages(cId);
            var image = images.Where(i => i.Id == iId).FirstOrDefault();
            if (image == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);
            return Tuple.Create(image, images);
        }

        /// <summary>
        /// Check the inputs to make sure that everything is ok.
        /// </summary>
        /// <param name="cId"></param>
        /// <param name="imageNameRegex"></param>
        /// <returns>Tuple with the first is a list of matching images, and the second is the complete list of images.</returns>
        private Tuple<ImageInfo[], ImageInfo[]> CheckInputs(int cId, string imageNameRegex)
        {
            var cl = CollectionInfoIO.LoadCollection(cId);
            if (cl == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            var images = ImageInfoIO.LoadAllImages(cId);
            var finder = new Regex(imageNameRegex);
            var image = images.Where(i => finder.Match(i.Name).Success).ToArray();
            return Tuple.Create(image, images);
        }
    }
}
